"""This module is a collection of base classes for everithing that will move in
the game"""
import pygame, math, sys
import ai, vecmath, particles, resource_manager, game
from pygame.locals import *
from random import *

class Ship(pygame.sprite.Sprite):
    """Controllable sprites, by a ai brain, incoming messages or a local player
    
    Instances may want to specify the imagefile on code. is_ai is a field,
    identifying the ai brain that will rid this ship (0 for human) (TODO)
    """
    ACC = 0.1 # acceleration; amount added to speed if we are accelerating (px/tick at 1X zoom)
    MAX_SPEED = 1 # max speed the ship can travel at (px/tick at 1X zoom)
    TURN = 2 # max turn speed, in degrees
    _image = None #image cached to use only one base image for each ship type
    def __init__(self, controller = None):
        pygame.sprite.Sprite.__init__(self)
        self.image = self._image.copy() # local copy of the global
        self.speed = [0, 0]
        self.position = list(self.rect.center)
        self.heading = 0
        self.health = 100
        self.next_prim, self.next_sec = 0, 0
        if not controller:
            self.controller = KeyController(self)
        else:
            self.controller = controller(self)
        self.dazzle = 0
        self.zoom = 1. # zoom to compare with the game zoom
        self.id = hash(self)

    def update(self, delta):
        """update(floating point fraction of a game loop tick)
        
        updates this ship state and position"""
        pygame.sprite.Sprite.update(self)
        if(game.zoom != self.zoom):
            self._update_sprite() # if needed
        
        game.cam.check_distance(self) # update the distance for this ship (will save reloop)
        
        
        # don't go too fast
        if (self.speed[0]**2 + self.speed[1]**2 > self.MAX_SPEED**2):
            self.speed[0] *= (1 - self.ACC)
            self.speed[1] *= (1 - self.ACC)
        
        self._update_position(delta)
        
        # collision detection
        col = pygame.sprite.spritecollideany(self, game.bullets)        
        if col and col.owner is not self:
            self._hit(col)
            col._hit()
        
        # leave a trail if seriously damaged
        if self.health < 10 and randint(0,4):            
            game.particles.add(particles.FireParticle(tuple(self.position),\
                                                       (0, 0),0, 0.1, 1, 150))
        
    def _update_position(self, delta):
        """Calls the controller to update this ship state and updates its
        position, depending on current position (both game and screen wise,
        acceleration and heading"""
        #if self.dazzle > 0:
        #    self.speed[0] -= self.speed[0] * (self.ACC / 10)
        #    self.speed[1] -= self.speed[1] * (self.ACC / 10)
        #    if randint(0, 1):
        #        self.left(delta)
        #    else:
        #        self.right(delta)
        #    self.dazzle -= 1
        #else:
        #    self.controller.control(delta)
        self.controller.control(delta)
            
        #w, h = (self.rect.w / 2, self.rect.h / 2)
        self.position[0] += self.speed[0] * delta
        self.position[1] += self.speed[1] * delta
        
        self.rect.center = ((self.position[0] * self.zoom) - game.vp[0],\
                            (self.position[1] * self.zoom) - game.vp[1])
    
    def _update_sprite(self):
        """updates the sprite after turning or zooming"""
        self.zoom = game.zoom
        self.image = pygame.transform.rotozoom(self._image, -self.heading, self.zoom)
        self.rect = self.image.get_rect()
    
    def fire_prim(self, delta = 1):
        """Stub for firing a weapon"""
        pass
    
    def fire_sec(self, delta = 1):
        """Stub for firing a weapon"""
        pass
    
    def hit(self, by):
        """hit(by(Bullet))
        Stub for being damaged (callback)"""
        pass
    
    def kiled(self, by):
        """killed(by(Ship))
        Stub for being killed (callback)"""
        pass
    
    def _hit(self, by):
        """Actions on hit event"""
        self.dazzle = 10
        self.controller.onhit(by) # tell the controller that we are hit
        self.health -= by.damage # perform damage TODO:do it on a function
        self.hit(by) # do the callback
        if self.health < 0: # die if you have to
            self._killed(by.owner)
            self.kill()

    def _killed(self, by):
        """Actions on killed event"""
        if self.health < 0:
            game.particles.add_emitter(particles.Explosion(tuple(self.position),\
                                                           2, self.speed))
        if self is game.cam.sprite: # if you are dead we need another player
            game.cam.add(by)
        
        self.killed(by) # do the callback
    
    def left(self, delta):
        """left(fraction of a game tick elapsed)
        Turn the ship left self.TURN degrees"""
        self.heading -= self.TURN * delta
        if self.heading < 0:
            self.heading = 360
        self._update_sprite()
   
    def right(self, delta):
        """right(fraction of a game tick elapsed)
        Turn the ship right self.TURN degrees"""
        self.heading += self.TURN * delta
        if self.heading > 360:
            self.heading = 0
        self._update_sprite()

    def accelerate(self, delta):
        """accelerate(fraction of a game tick elapsed)
        Accelerates the ship self.ACC units"""
        self.speed[0] += self.ACC * math.cos(math.radians(self.heading)) * delta
        self.speed[1] += self.ACC * math.sin(math.radians(self.heading)) * delta
    
    def deccelerate(self, delta):
        """deccelerate(fraction of a game tick elapsed)
        Deccerates the ship -self.ACC units"""
        self.speed[0] -= self.ACC * math.cos(math.radians(self.heading)) * delta
        self.speed[1] -= self.ACC * math.sin(math.radians(self.heading)) * delta

    def serialize(self, with_class = False):
        """Returns a serialized representation of this ship"""
        serial = []
        if with_class:
            serial.append(self.__class__)
        
        serial.append(self.position)
        serial.append(self.speed)
        serial.append(self.heading)
        serial.append(self.health)
        serial.append(self.controller.actionmask())
        
        return serial
class KeyController(game.Controller):
    """This class should probably go in the ai module, since it's shape is
    identical to an ai brain, save instead of making decisions, it probes for
    key strokes to perform actions"""
    def onhit(self, by):
        pass
    def control(self, delta):
        #poll for keys TODO: read key bindings from a file
        k = pygame.key.get_pressed()
        if k[K_LEFT]:
            self.ship.left(delta)
        if k[K_RIGHT]:
            self.ship.right(delta)
        if k[K_UP]:
            self.ship.accelerate(delta)
        if k[K_DOWN]:
            #self.ship.deccelerate(delta)
            #game.particles.add(particles.SmokeParticle(self.ship.position,\
            #                                           (-2+random()*4,-2+random()*4),\
            #                                            randint(-10, 10),  0.01, 1.15))
            #game.particles.add_emitter(particles.SmokeEmitter(1, self.ship.position,\
            #                                           (2,4),(5, 10)))
            pass
        if k[K_s]:
            self.ship.fire_sec()
        if k[K_d]:
            self.ship.fire_prim()

class Bullet(pygame.sprite.Sprite):
    """A bullet fired by a ship"""
    _image = resource_manager.load_image('bullet.png', -1)[0]
    _hit_image = resource_manager.load_image('hit.png', -1)[0]
    def __init__(self, owner, speed, position, ttl = 1000, damage = 5):
        """Recieves as argument, the ship owning the bullet, imagefile name,
    speed (x, y), position (x, y) and time to live in milliseconds"""
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.transform.rotate(self._image, -owner.heading)
        self.position = list(vecmath.add(position, owner.speed))
        self.rect = self.image.get_rect(center=vecmath.dif(game.vp, position))
        self.speed = list(speed)
        self.ttl = pygame.time.get_ticks() + ttl
        self.owner = owner
        self.damage = damage
        game.bullets.add(self)
    
    def _hit(self):
        """collision action"""
        if game.effects:
            game.effects.blit(pygame.transform.rotozoom(self._hit_image, 0,\
                                                        game.zoom), self.rect.center)
        #game.effects.blit(self._hit_image, self.rect.center)
        self.kill()
    
    def update(self, delta):
        """Updates the position(both game and screenwise) based on a constant speed."""
        self.position[0] += self.speed[0] * delta
        self.position[1] += self.speed[1] * delta
        self.rect.center = ((self.position[0] * game.zoom) - game.vp[0],\
                            (self.position[1] * game.zoom) - game.vp[1])
        if pygame.time.get_ticks() > self.ttl:
            self.kill()

import game #to avoid circular import (need testing)